//
//  SLClassInfo.h
//  SLModel
//
//  Created by S_Line on 2018/6/21.
//  Copyright © 2018年 S_Line. All rights reserved.
//

#import <Foundation/Foundation.h>

#import <objc/runtime.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_OPTIONS(NSUInteger, SLEncodingType) {
    SLEncodingTypeMask          = 0xFF,
    SLEncodingTypeUnknown       = 0,
    SLEncodingTypeVoid          = 1,
    SLEncodingTypeBool          = 2,
    SLEncodingTypeInt8          = 3,
    SLEncodingTypeUInt8         = 4,
    SLEncodingTypeInt16         = 5,
    SLEncodingTypeUInt16        = 6,
    SLEncodingTypeInt32         = 7,
    SLEncodingTypeUInt32        = 8,
    SLEncodingTypeInt64         = 9,
    SLEncodingTypeUInt64        = 10,
    SLEncodingTypeFloat         = 11,
    SLEncodingTypeDouble        = 12,
    SLEncodingTypeLongDouble    = 13,
    SLEncodingTypeObject        = 14,
    SLEncodingTypeClass         = 15,
    SLEncodingTypeSel           = 16,
    SLEncodingTypeBlock         = 17,
    SLEncodingTypePointer       = 18,
    SLEncodingTypeStruct        = 19,
    SLEncodingTypeUnion         = 20,
    SLEncodingTypeCString       = 21,
    SLEncodingTypeCArray        = 22,
    
    SLEncodingTypeQualifierMask     = 0xFF00,
    SLEncodingTypeQualifierConst    = 1 << 8,
    SLEncodingTypeQualifierIn       = 1 << 9,
    SLEncodingTypeQualifierInOut    = 1 << 10,
    SLEncodingTypeQualifierOut      = 1 << 11,
    SLEncodingTypeQualifierBycopy   = 1 << 12,
    SLEncodingTypeQualifierByRef    = 1 << 13,
    SLEncodingTypeQualifierOneway   = 1 << 14,
    
    SLEncodingTypePropertyMask          = 0xFF0000,
    SLEncodingTypePropertyReadonly      = 1 << 16,
    SLEncodingTypePropertyCopy          = 1 << 17,
    SLEncodingTypePropertyRetain        = 1 << 18,
    SLEncodingTypePropertyNonatomic     = 1 << 19,
    SLEncodingTypePropertyWeak          = 1 << 20,
    SLEncodingTypePropertyCustomGetter  = 1 << 21,
    SLEncodingTypePropertyCustomSetter  = 1 << 22,
    SLEncodingTypePropertyDynamic       = 1 << 23
};

/**
 根据typeEncoding字符串获取type
 @param typeEncoding 一个Type-Encoding字符串.
 @return The encoding type
 */
SLEncodingType SLEncodingGetType(const char *typeEncoding);

@interface SLClassIvarInfo: NSObject

@property (nonatomic, assign, readonly) Ivar ivar;
@property (nonatomic, strong, readonly) NSString *name;             /// Ivar's name
@property (nonatomic, assign, readonly) ptrdiff_t offset;           /// Ivar's offset
@property (nonatomic, strong, readonly) NSString *typeEncoding;     /// Ivar's type encoding
@property (nonatomic, assign, readonly) SLEncodingType type;        /// Ivar's type

/**
 Creates and returns an ivar info object
 
 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithIvar:(Ivar)ivar;

@end

@interface SLClassMethodInfo: NSObject

@property (nonatomic, assign, readonly) Method method;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) SEL sel;
@property (nonatomic, assign, readonly) IMP imp;
@property (nonatomic, strong, readonly) NSString *typeEncoding;
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;
@property (nonatomic, nullable, strong, readonly) NSArray<NSString *> *argumentTypeEncodings;

/**
 Creates and returns a method info object.
 
 @param method method opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithMethod:(Method)method;

@end

@interface SLClassPropertyInfo: NSObject

@property (nonatomic, assign, readonly) objc_property_t property;
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) SLEncodingType type;
@property (nonatomic, strong, readonly) NSString *typeEncoding;
@property (nonatomic, strong, readonly) NSString *ivarName;
@property (nullable, nonatomic, assign, readonly) Class cls;
@property (nullable, nonatomic, strong, readonly) NSArray<NSArray *> *protocols;
@property (nonatomic, assign, readonly) SEL getter;
@property (nonatomic, assign, readonly) SEL setter;

/**
 Creates and returns a property info object.
 
 @param property property opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithProperty:(objc_property_t)property;

@end

@interface SLClassInfo : NSObject

@property (nonatomic, assign, readonly) Class cls;
@property (nullable, nonatomic, assign, readonly) Class superCls;
@property (nullable, nonatomic, assign, readonly) Class metaCls;
@property (nonatomic, readonly) BOOL isMeta;
@property (nonatomic, strong ,readonly) NSString *name;
@property (nullable, nonatomic, strong, readonly) SLClassInfo *superClassInfo;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, SLClassIvarInfo *> *ivarInfos;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, SLClassMethodInfo *> *methodInfos;
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, SLClassPropertyInfo *> *propertyInfos;

/**
 If the class is changed (for example: you add a method to this class with 'class_addMethod()'), you should call this method to refresh the class info cache.
 
 After called this method, 'needUpdate' will returns 'YES', and you should call 'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info.
 */
- (void)setNeedUpdate;

/**
 If this method returns 'YES', you should stop using this instance and call 'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info.
 
 @return Whether this class info need update.
 */
- (BOOL)needUpdate;

/**
 Get the class info of a specified Class.
 
 @discussion this method will cache the class info and super-class info at the first access to the Class. This method is thread-safe.
 
 @param cls a Class
 @return A class info object, or nil if an error occurs.
 */
+ (nullable instancetype)classInfoWithClass:(Class)cls;

/**
 Get the class info of a specified Class.
 
 @discussion this method will cache the class info and super-class info at the first access to the Class. This method is thread-safe.
 
 @param className a class name
 @return A class info object, or nil if an error occurs.
 */
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;


@end

NS_ASSUME_NONNULL_END
